<?php
/**
 * files.inc
 * File handler module.
 * @copyright	Copyright(c) 2002-2010 Bjorn Winberg
 * @author		Bjorn Winberg <cms@anomalye.net>
 * @license		http://www.gnu.org/licenses/gpl.html GNU General Public License version 2
 * @package		AnomalyWiki
 */
 
class Files extends Module{
	public static $MODULE_UID = 'files';
	const SQL_TABLE_FILE = 'CREATE TABLE IF NOT EXISTS files_file (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, timestamp INTEGER, version INTEGER, size INTEGER, author_id INTEGER, directory_id INTEGER, downloads INTEGER, data BLOB)';
	const SQL_TABLE_DIRECTORY = 'CREATE TABLE IF NOT EXISTS files_directory (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, parent_id INTEGER)';
	
	const PARAM_MAX_ACTION = 64;
	const PARAM_MAX_DIR = 32;
	protected $mSystem;	
	
	/**
	 *
	 */
	public function __construct(){
		parent::__construct();
		$this->loadSettings();
		$this->mSystem = ModuleFactory::loadModule('system');	
	}
	
	/**
	 *
	 */
	public function generateView(){
		$db = ModuleFactory::getDb();
		$rootResult = $db->dbQuery('SELECT * FROM files_directory WHERE name=""');
		$rootId = false;
		if($rootRow = $rootResult->fetchArray(SQLITE3_ASSOC)){
			$rootId = $rootRow['id'];
		}
		$inputDirId = $this->getParamInt('dir', 1, 0, $rootId);
		$inputFileId = $this->getParamInt('file', 1, 0);
		$inputAction = $this->getParamEnc('a', 1, self::PARAM_MAX_ACTION);
		$params = array();
		$params['dir'] = false;
		$params['parent'] = false;
		$params['action'] = $inputAction;
		$dirResult = $db->dbQuery('SELECT * FROM files_directory WHERE id="' . $db->dbEscape($inputDirId) . '"');
		if($dirRow = $dirResult->fetchArray(SQLITE3_ASSOC)){
			$params['parent'] = $dirRow['parent_id'];
			$params['dir'] = $dirRow['id'];
		}
		
		if($inputAction === 'create_dir'){
			if(!$this->requireAuthorization('FILES/UPLOAD')){
				$this->mSystem->feedbackWarn($this->mSystem->localString('ERROR_PERMISSION_DENIED'));
				return false;
			}
			$inputDirName = $this->getParamUId('name', 1, self::PARAM_MAX_DIR);
			if(!$this->validateName($inputDirName)){
				$this->mSystem->feedbackWarn($this->localString('ERROR_INVALID_NAME', array()));
			}
			elseif($params['dir'] !== false && $inputDirName !== false){
				$nameResult = $db->dbQuery('SELECT * FROM files_directory WHERE name="' . $db->dbEscape($inputDirName) . '" AND parent_id="' . $db->dbEscape($inputDirId) . '"');
				if(!$nameRow = $nameResult->fetchArray(SQLITE3_ASSOC)){
					$dbValues = array('name' => $inputDirName, 'parent_id' => $inputDirId);
					$db->dbExecute('INSERT INTO files_directory' . $db->dbValues($dbValues));
					$this->mSystem->feedbackInfo($this->localString('OK_CREATE_DIR'));
					$this->mSystem->feedbackLog(static::$MODULE_UID, $this->localString('LOG_CREATED_ARTICLE', array($this->getSafeStr($inputDirName))));
				}
				else{
					$this->mSystem->feedbackWarn($this->localString('ERROR_DIR_EXISTS', array()));
				}
			}
			else{
				$this->mSystem->feedbackWarn($this->localString('ERROR_PARENT_NOT_FOUND', array()));
			}
		}
		elseif($inputAction === 'upload'){
			if(!$this->requireAuthorization('FILES/UPLOAD')){
				$this->mSystem->feedbackWarn($this->mSystem->localString('ERROR_PERMISSION_DENIED'));
				return false;
			}
			$this->uploadFile($db, $inputDirId);
		}
		elseif($inputFileId !== false){
			if(!$this->requireAuthorization('FILES/DOWNLOAD')){
				header("HTTP/1.0 403 Forbidden");
				header("Status: 403 Forbidden");
				return false;
			}
			return $this->downloadFile($db, $inputFileId);
		}
		
		if(!$this->requireAuthorization('FILES/BROWSE')){
			$this->mSystem->feedbackWarn($this->mSystem->localString('ERROR_PERMISSION_DENIED'));
			return false;
		}

		$params['pathString'] = '';
		$currentId = $inputDirId;
		do{
			$dirResult = $db->dbQuery('SELECT * FROM files_directory WHERE id="' . $db->dbEscape($currentId) . '"');
			if($dirRow = $dirResult->fetchArray(SQLITE3_ASSOC)){
				$params['pathString'] = $this->getSafeStr($dirRow['name']) . $this->localSetting('DIRECTORY_SEPARATOR') . $params['pathString'];
				$currentId = $dirRow['parent_id'];
			}
			else{
				$this->mSystem->feedbackWarn($this->localString('ERROR_PARENT_NOT_FOUND', array()));
				return false;
			}
		}while($currentId != '');
		
		$dirListResult = $db->dbQuery('SELECT * FROM files_directory WHERE parent_id="' . $db->dbEscape($params['dir']) . '"');
		$params['dirList'] = array();
		while($row = $dirListResult->fetchArray(SQLITE3_ASSOC)){ 
			$params['dirList'][] = $row;
		}
		$fileListResult = $db->dbQuery('SELECT * FROM files_file WHERE directory_id="' . $db->dbEscape($params['dir']) . '"');
		$params['fileList'] = array();
		while($row = $fileListResult->fetchArray(SQLITE3_ASSOC)){ 
			$params['fileList'][] = $row;
		}
		return $this->generateTemplate('files_browse.inc', $params);
	}
	
	/**
	 *
	 */
	public function firstTimeSetup(){
		$db = ModuleFactory::getDb();
		$auth = ModuleFactory::loadModule('auth');
		$browseId = $auth->addAction($db, rawurlencode('FILES/BROWSE'), 1);
		$uploadId = $auth->addAction($db, rawurlencode('FILES/UPLOAD'), 1);
		$downloadId = $auth->addAction($db, rawurlencode('FILES/DOWNLOAD'), 1);
		$auth->grantAction($db, $auth::ADMIN_GROUP_ID, $browseId);
		$auth->grantAction($db, $auth::ADMIN_GROUP_ID, $uploadId);
		$auth->grantAction($db, $auth::ADMIN_GROUP_ID, $downloadId);
		$db->dbExecute(self::SQL_TABLE_FILE);
		$db->dbExecute(self::SQL_TABLE_DIRECTORY);
		$dbValues = array('name' => '', 'parent_id' => '');
		$db->dbExecute('INSERT INTO files_directory' . $db->dbValues($dbValues));
	}
	
	function downloadFile($aDb, $aFileId){
		$this->mSystem->mOutputMode = SystemModule::OUTPUT_CUSTOM;
		$lookupResult = $aDb->dbQuery('SELECT * FROM files_file WHERE id="' . $aDb->dbEscape($aFileId) . '"');
		if($row = $lookupResult->fetchArray(SQLITE3_ASSOC)){
			$contentType = $this->getMimeType($row['name']);
			//$maxAge = $GLOBALS['_S']['files']['HTTP_CACHE_AGE'];
			header('Content-Type: ' . $contentType);
			header('Accept-Ranges: bytes');
			header('Content-Disposition: attachment; filename=' . $this->getSafeStr($row['name']));
			$cacheHeader1 = 'Cache-Control: public';
			//$cacheHeader2 = 'Expires: ' . gmdate('r', time() + $maxAge);
			$cacheHeader3 = "Pragma: public";
			header('Content-Length: ' . $row['size']);
			header($cacheHeader1);
			//header($cacheHeader2);
			header($cacheHeader3);
			$aDb->dbQuery('UPDATE files_file SET downloads = downloads+1 WHERE id="' . $aDb->dbEscape($aFileId) . '"');
			echo $row['data'];
		}
		else{
			header("HTTP/1.0 404 Not found");
			header("Status: 404 Not found");
			return false;
		}
	}

	function uploadFile($aDb, $aDirId, $aFileName = false){
		if(!isset($_FILES)){
			return false;
		}
		if($_FILES === array()){
			$this->mSystem->feedbackWarn($this->localString('ERROR_TOO_BIG'));
			return false;
		}
		$fileSize = $_FILES['userfile']['size'];
		if($fileSize > $this->getUploadSize() || $_FILES['userfile']['error'] == 2){
			$this->mSystem->feedbackWarn($this->localString('ERROR_TOO_BIG'));
			return false;
		}
		if($aFileName !== false){
			$fileName = $aFileName;
		}
		else{
			$fileName = strtolower(rawurlencode($_FILES['userfile']['name']));
		}
		if(!$this->validateName($fileName)){
			$this->mSystem->feedbackWarn($this->localString('ERROR_INVALID_NAME'));
			return false;
		}
		if($_FILES['userfile']['error'] == 0){
			$submitTime = time();
			$i = 0;
			if(is_uploaded_file($_FILES['userfile']['tmp_name'])){
				$version = 0;
				$fileContent = file_get_contents($_FILES['userfile']['tmp_name']);
				$fileContent = "X'" . bin2hex($fileContent) . "'";
				$dbValues = array('name' => $fileName, 'timestamp' => $submitTime, 'version' => $version, 'size' => $fileSize, 'author_id' => $_SESSION['auth']['user']['id'], 'directory_id' => $aDirId, 'downloads' => 0, 'data' => $fileContent);
				$aDb->dbExecute('INSERT INTO files_file' . $aDb->dbValues($dbValues, array('data' => true)));

				$this->mSystem->feedbackInfo($this->localString('OK_UPLOAD_FILE'));
				$this->mSystem->feedbackLog(static::$MODULE_UID, $this->localString('LOG_UPLOAD_FILE', array($this->getSafeStr($fileName))));
				return true;
			}
			else{
				$this->mSystem->feedbackWarn($this->localString('ERROR_WITH_CODE', array(-1)));
			}
		}
		else{
			$this->mSystem->feedbackWarn($this->localString('ERROR_WITH_CODE', array($_FILES['userfile']['error'])));
		}
		return false;
	}
	
	/**
	 * Compare php and user settings to calculate the maximum upload size.
	 * @return int The maximum upload size.
	 */
	function getUploadSize(){
		$max = 0;
		$mem = $this->parseUploadSize(ini_get('memory_limit'));
		$post = $this->parseUploadSize(ini_get('post_max_size'));
		$file = $this->parseUploadSize(ini_get('upload_max_filesize'));
		$opt = $this->parseUploadSize($this->localSetting('MAX_UPLOAD_SIZE'));
		if($mem == -1){
			$max = min($post, $file, $opt);
		}
		else{
			$max = min($post, $file, $mem, $opt);
		}
		return $max;
	}
	
	/**
	 * Convert an .ini size string to an integer value.
	 * @param string $aSize A size string from an ini file.
	 * @return int An integer size or -1 if an error occured.
	 */
	function parseUploadSize($aSize){
		if($aSize == ''){
			$aSize = -1;
		}
		$aSize = str_replace('G', '000000000', $aSize);
		$aSize = str_replace('M', '000000', $aSize);
		$aSize = str_replace('k', '000', $aSize);
		return (integer)$aSize;
	}
	
	function validateName($aStr){
		if(strpos($aStr, strtolower(rawurlencode($this->localSetting('DIRECTORY_SEPARATOR')))) !== false){
			return false;
		}
		return true;
	}
	
	function getMimeType($aFilename){
		$bits = explode(' ', $this->localSetting('MIME_CONTENT_TYPES'));
		$mimeTypes = array();
		foreach($bits as $bit){
			$typeArray = explode(',', $bit);
			$mimeTypes[$typeArray[0]] = $typeArray[1];
		}
		$fileBits = explode('.', $aFilename);
		$bitCount = count($fileBits);
		$type = array_pop($fileBits);
		if(isset($mimeTypes[$type])){
			return $mimeTypes[$type];
		}
		return 'application/force-download';
	}
	
	/**
	 *
	 */
	public function handleParseTag($aParser, $aTag){
		$tagBits = explode(rawurlencode($this->mSystem->localSetting('COMMAND_PARAMETER_SEPARATOR')), $aTag);
		$db = ModuleFactory::getDb();
		$tagCount = count($tagBits);
		if($tagCount > 2 && $tagBits[1] == rawurlencode('image')){
			$uidPath = strtolower($tagBits[2]);
			$bits = explode(strtolower(rawurlencode($this->localSetting('DIRECTORY_SEPARATOR'))), $uidPath);
			$uidFileName = array_pop($bits);
			$dirId = '';
			foreach($bits as $bit){
				$dirResult = $db->dbQuery('SELECT * FROM files_directory WHERE name="' . $db->dbEscape($bit) . '" AND parent_id="' . $db->dbEscape($dirId) . '"');
				if($dirRow = $dirResult->fetchArray(SQLITE3_ASSOC)){
					$dirId = $dirRow['id'];
				}
				else{
					$this->mSystem->feedbackWarn($this->localString('ERROR_FILE_NOT_FOUND', array()));
					return '';
				}
			}
			$fileResult = $db->dbQuery('SELECT * FROM files_file WHERE name="' . $db->dbEscape($uidFileName) . '" AND directory_id="' . $db->dbEscape($dirId) . '"');
			if($fileRow = $fileResult->fetchArray(SQLITE3_ASSOC)){
				return '<img class="parserImage" src="' . $this->generateUrl('files', array('dir' => $dirId, 'file' => $fileRow['id'])) . '" alt="" />';
			}
			else{
				$this->mSystem->feedbackWarn($this->localString('ERROR_FILE_NOT_FOUND', array()));
				return '';
			}
		}
		else{
			$this->mSystem->feedbackWarn($this->mSystem->localString('PARSE_INVALID_VARIABLE', array($this->getSafeStr($tagBits[1]))));
		}
	}
}
?>
